﻿using System.Collections.Generic;

namespace QAToolsAddin {
    class Combinaison {
        private string[] _dataset;
        private object[,] _resultArray;
        private int[] _ptr;
        private int _row = 0;

        internal static object[,] Get(string[] list, int k, bool withOrder, bool withRepetition, bool withLowerGroups) {
            return new Combinaison(list, k, withOrder, withRepetition, withLowerGroups)._resultArray;
        }

        internal static object[,] Get(List<string[]> valuesList) {
            return new Combinaison(valuesList)._resultArray;
        }

        public Combinaison(string[] list, int k, bool withPermutation, bool withRepetition, bool withLowerGroups) {
            _dataset = list;
            int n = list.Length;
            int nbRow = 0;
            for (int kk = withLowerGroups ? 1 : k; kk <= k; kk++)
                nbRow += CombinaisonCount(n, kk, withPermutation, withRepetition);
            _resultArray = ExcelArray<object>.Create(nbRow, k);
            _row = 0;
            for (int kk = withLowerGroups ? 1 : k; kk <= k; kk++) {
                if (withPermutation) {
                    if (withRepetition)
                        PermuteRepeat(n, kk, 0);
                    else
                        Permute(n, kk, 0);
                } else {
                    if (withRepetition)
                        CombinateRepeat(n, kk, 0);
                    else
                        Combinate(n, kk, 0);
                }
            }
        }

        public int CombinaisonCount(int n, int k, bool withPermutation, bool withRepetition) {
            _row = 0;
            if (withPermutation) {
                if (withRepetition)
                    PermuteRepeatCount(n, k, 0);
                //return (int)checked(Math.Pow(n, k));
                else
                    PermuteCount(n, k, 0);
                //return (int)checked(_factorial[n] / _factorial[n - k]);
                // (int)Math.Round((n / (n - k)) * Math.Exp(LogGamma(n) - LogGamma(n - k))); //nPk
            } else {
                if (withRepetition)
                    CombinateRepeatCount(n, k, 0);
                //return (int)checked(_factorial[n + k - 1] / (_factorial[k] * _factorial[n - 1]));
                else
                    CombinateCount(n, k, 0);
                //return (int)checked(Factorial(n) / (Factorial(n - k) * Factorial(k)));
                //return (int)Math.Round( ( n/(n-k)/k) * Math.Exp( LogGamma(n) - LogGamma(n-k)) );
            }
            int nbCmbi = _row;
            return nbCmbi;
        }

        private int[] NewIndexedPtr(int len) {
            int[] ptr = new int[len];
            for (int i = 0; i < len; i++)
                ptr[i] = i;
            return ptr;
        }

        public Combinaison(List<string[]> valuesList) {
            int nbRow = 1;
            int nbCol = valuesList.Count;
            foreach (string[] values in valuesList)
                nbRow *= values.Length;
            _resultArray = ExcelArray<object>.Create(nbRow, nbCol);
            for (int c = 0, ci = nbCol, rpt = 1; c < nbCol; c++, ci--) {
                var values = valuesList[c];
                var len = values.Length;
                for (int ri = 0; ri < nbRow; ) {
                    for (int v = 0; v < len; v++) {
                        for (int j = 0; j < rpt; j++)
                            _resultArray[++ri, ci] = values[v];
                    }
                }
                rpt *= len;
            }
        }

        private void Combinate(int n, int k, int deep) {
            if (deep == 0)
                _ptr = new int[k];
            else
                _ptr[deep] = _ptr[deep - 1] + 1;
            while (_ptr[deep] < (n - k + 1 + deep)) {
                if (deep < k - 1)
                    Combinate(n, k, deep + 1);
                else {
                    _row++;
                    for (int c = 0; c < k; c++)
                        _resultArray[_row, c + 1] = _dataset[_ptr[c]];
                }
                _ptr[deep]++;
            }
        }

        private void CombinateCount(int n, int k, int deep) {
            if (deep == 0)
                _ptr = new int[k];
            else
                _ptr[deep] = _ptr[deep - 1] + 1;
            while (_ptr[deep] < (n - k + 1 + deep)) {
                if (deep < k - 1)
                    CombinateCount(n, k, deep + 1);
                else {
                    _row++;
                }
                _ptr[deep]++;
            }
        }

        private void CombinateRepeat(int n, int k, int deep) {
            if (deep == 0)
                _ptr = new int[k];
            if (deep == k) {
                _row++;
                for (int c = 0; c < k; c++)
                    _resultArray[_row, c + 1] = _dataset[_ptr[c]];
                return;
            }
            for (int i = 0; i < n; ++i) {
                _ptr[deep] = i;
                if (deep > 0 && _ptr[deep] < _ptr[deep - 1])
                    continue;
                CombinateRepeat(n, k, deep + 1);
            }
        }

        private void CombinateRepeatCount(int n, int k, int deep) {
            if (deep == 0)
                _ptr = new int[n];
            if (deep == k) {
                _row++;
                return;
            }
            for (int i = 0; i < n; ++i) {
                _ptr[deep] = i;
                if (deep > 0 && _ptr[deep] < _ptr[deep - 1])
                    continue;
                CombinateRepeatCount(n, k, deep + 1);
            }
        }

        private void Permute(int n, int k, int deep) {
            if (deep == 0)
                _ptr = NewIndexedPtr(n);
            if (deep == k) {
                _row++;
                for (int c = 0, ci = 1; c < k; c++, ci++)
                    _resultArray[_row, ci] = _dataset[_ptr[c]];
            } else {
                for (int i = deep; i < n; i++) {
                    int t = _ptr[i];
                    _ptr[i] = _ptr[deep];
                    _ptr[deep] = t;
                    Permute(n, k, deep + 1);
                    _ptr[deep] = _ptr[i];
                    _ptr[i] = t;
                }
            }
        }

        private void PermuteCount(int n, int k, int deep) {
            if (deep == k) {
                _row++;
            } else {
                for (int i = deep; i < n; i++)
                    PermuteCount(n, k, deep + 1);
            }
        }

        private void PermuteRepeat(int n, int k, int deep) {
            if (deep == 0)
                _ptr = new int[n];
            if (deep == k) {
                _row++;
                for (int c = 0; c < k; c++)
                    _resultArray[_row, c + 1] = _dataset[_ptr[c]];
                return;
            }
            for (int i = 0; i < n; ++i) {
                _ptr[deep] = i;
                PermuteRepeat(n, k, deep + 1);
            }
        }

        private void PermuteRepeatCount(int n, int k, int deep) {
            if (deep == k) {
                _row++;
                return;
            }
            for (int i = 0; i < n; ++i)
                PermuteRepeatCount(n, k, deep + 1);
        }

    }
}
